package br.edu.ufcg.splab.simulator.clique;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;

import br.edu.ufcg.splab.core.InterfaceGenerate;
import br.edu.ufcg.splab.core.InterfaceGraph;
import br.edu.ufcg.splab.core.graph.Tree;
import br.edu.ufcg.splab.exceptions.LTSBTException;
import br.edu.ufcg.splab.generator.ConvertGraphToTree;
import br.edu.ufcg.splab.generator.TestCase;
import br.edu.ufcg.splab.generator.TestCaseGenerator;
import br.edu.ufcg.splab.generator.TestSuite;
import br.edu.ufcg.splab.parser.GraphToAnnotatedGraphParser;
import br.edu.ufcg.splab.simulator.clique.graph.UndirectedGraph;
import br.edu.ufcg.splab.simulator.clique.graph.Vertex;
import br.edu.ufcg.splab.techniques.reduction.functions.similarity.DistanceFunction;
import br.edu.ufcg.splab.techniques.reduction.functions.similarity.SymmetricNormalisedCompressionDistance;
import br.edu.ufcg.splab.techniques.reduction.strategies.SimilarityMatrix;
import br.edu.ufcg.splab.util.Controller;
import br.edu.ufcg.splab.util.MapFaultAndTestSuite;
import br.edu.ufcg.splab.util.MapTestSuite;
import br.edu.ufcg.splab.util.ReadTGF;

/**
 * This class is responsible for assigning one set of defects and failures from the test suite.
 * 
 **/
public class AssignerDefect2 {
	private Map<Integer, TestCase> mapTestSuite;
	
	private TestSuite testSuite;
	private DistanceFunction function;
	private double percentageMaximum; 
	private double percentageOfDefects;
	
	/**
	 * Returns the relationship between defects and failures in a test suite.
	 * @param testSuite The test suite in which will be assigned defects.
	 * @param function The distance function to define the relationship between two test cases.
	 * @param percentageMaximum The percentage maximum relative the minimum value in the matrix to define a strong relation between two test cases.
	 * @param percentageOfDefects The percentage of defects to be selected from the test suite.
	 * @return A set of defects and failures from the test suite.
	 */
	public Map<Integer, List<TestCase>> assignDefects(TestSuite testSuite, DistanceFunction function, double percentageMaximum, double percentageOfDefects) {
		this.testSuite = testSuite;
		this.function = function;
		this.percentageMaximum = percentageMaximum;
		this.percentageOfDefects = percentageOfDefects;
		
		createMapTestSuite();
		double maximumValue = getMaximumValue();
		List<TestCase> testCases = chooseRandomTestCases();
		return defineDefects(testCases, maximumValue);
	}
	
	/*
	 * Creates the map to which the test cases are mapped.
	 */
	private void createMapTestSuite(){
		mapTestSuite = new HashMap<Integer, TestCase>();
		int i = 1;
		for(TestCase testCase: testSuite.getTestSuite()){
			mapTestSuite.put(i++, testCase);
		}
	}
	
	/*
	 * Returns the key to which the specified test case is mapped,
     * or -1 if this map contains no mapping for the test case.
	 * @param testCase The test case whose associated key is to be returned.
	 * @return The key to which the specified test case is mapped or -1 
	 * if this map contains no mapping for the test case.
	 */
	private int getValueTestCase(TestCase testCase){
		if(mapTestSuite.containsValue(testCase)){
			Set<Entry<Integer, TestCase>> set = mapTestSuite.entrySet();
			for(Entry<Integer, TestCase> entry:set){
				if(entry.getValue().equals(testCase)){
					return entry.getKey();
				}
			}
		};
		return -1;
	}
	
	/*
	 * Sets the minimum value for a strong relation between two test cases on matrix from of a distance function.
	 * @return The minimum value to define a strong relationship between two test cases.
	 */
	private double getMaximumValue(){
		SimilarityMatrix matrix = new SimilarityMatrix();
		double minimumValue = matrix.getMinimumValue(testSuite, function);
		return minimumValue + (minimumValue * percentageMaximum);
	}
	
	/*
	 * Returns a set of test cases selected randomly.
	 * @return A set of test cases selected randomly.
	 */
	private List<TestCase> chooseRandomTestCases(){
		List<TestCase> testCases = testSuite.getTestSuite();
		List<TestCase> failures = new ArrayList<TestCase>();
		int numberOfFailures = (int) Math.round(testCases.size() * (percentageOfDefects/100.00)); 	
		for (int i = 0; i < numberOfFailures; i++) {
			if(!testCases.isEmpty()){
				if(!testCases.isEmpty()){
					TestCase randomTC = testCases.get(new Random().nextInt(testCases.size()));
					failures.add(randomTC);
					testCases.remove(randomTC);
				}
			}
		}
		return failures;
	}
	
	/*
	 * This method defines the defects among a set of test cases that fail.
	 * @param testCases The test cases that fail.
	 * @param minimumValue The minimum value to define a strong relationship between two test cases.
	 * @return The defects among a set of test cases that fail.
	 */
	private Map<Integer, List<TestCase>> defineDefects(List<TestCase> testCases, double minimumValue){
		UndirectedGraph graph = createUndirectedGraph(testCases, minimumValue);		
		return getDefects(graph);
	}
	
	/*
	 * Creates an undirected graph from the test cases that fail and the relationship among this test cases.
	 */
	private UndirectedGraph createUndirectedGraph(List<TestCase> testCases, double maximumValue){
		SimilarityMatrix matrix = new SimilarityMatrix();
		TestSuite testSuite = new TestSuite();
		testSuite.addAll(testCases);
		double[][] m = matrix.createMatrix(testSuite, function);
		
		UndirectedGraph graph = new UndirectedGraph();
		for(TestCase tc: testCases){
			 Vertex vertex = new Vertex(this.getValueTestCase(tc));
			 graph.addVertex(vertex);
		}
		
		for (int line = 0; line < m.length; line++) {
			for (int column = line + 1; column < m.length; column++) {
				if(m[line][column] <= maximumValue){
					Vertex v1 = graph.getVertices().get(line);
					Vertex v2 = graph.getVertices().get(column);
					graph.addEdge(v1, v2);
				}
			}
		}
				
		return graph;
	}
	
	/*
	 * Defines the defects from the undirected graph.
	 */
	private Map<Integer,List<TestCase>> getDefects(UndirectedGraph graph){
		//identification of each fault
		Map<Integer,List<TestCase>> mapFaults = new HashMap<Integer, List<TestCase>>();
		
		List<Vertex> noNeighbors = graph.getVertexNoNeighbors();		
		for(Vertex vertex:noNeighbors){
			graph.removeVertex(vertex);
		}		
		
		Clique max = new Clique(graph);
	    Set<Set<Vertex>> cliques = max.getCliques();
	    int countFault = 1;
	    for(Set<Vertex> set:cliques){
	    	List<TestCase> list = new ArrayList<TestCase>();
			for (Vertex vertex : set) {
				list.add(mapTestSuite.get(vertex.getId()));
			}
			mapFaults.put(countFault, list);
			countFault++;
		}
	    
	    for(Vertex vertex:noNeighbors){
	    	List<TestCase> list = new ArrayList<TestCase>();
	    	list.add(mapTestSuite.get(vertex.getId()));
	    	mapFaults.put(countFault, list);
			countFault++;
	    }
	    
		return mapFaults;		
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		for (int i = 0; i < 1; i++) {
			try{
				String tgf = "/home/ana/Documentos/Doutorado/modelo_falhas/UC_CB04-annotated-ofuscado.tgf";
				
				ReadTGF read = new ReadTGF();			
				InterfaceGraph graph = read.getGraph(tgf);				
				GraphToAnnotatedGraphParser parser = new GraphToAnnotatedGraphParser();
				InterfaceGraph annotatedGraph = parser.graphContraction(graph);
				
				InterfaceGenerate generator = new TestCaseGenerator();
				ConvertGraphToTree convertsGraphToTree = new ConvertGraphToTree();
				Tree tree = convertsGraphToTree.converterGraph(annotatedGraph);
				tree.expand(1);
				TestSuite testSuite = generator.generate(tree);			
				Controller controller = new Controller();
				TestSuite validTS = controller.getValidTS(testSuite);
				MapTestSuite mTS = new MapTestSuite(testSuite);
				
				SymmetricNormalisedCompressionDistance ncd = new SymmetricNormalisedCompressionDistance();
				
				AssignerDefect2 faults = new AssignerDefect2();
				MapFaultAndTestSuite mapTS = new MapFaultAndTestSuite(testSuite);
				Map<Integer, List<TestCase>> map = faults.assignDefects(validTS, ncd, 76.28, 35.71);
				Set<Entry<Integer, List<TestCase>>> s = map.entrySet();
				Iterator<Entry<Integer, List<TestCase>>> it = s.iterator();
				while(it.hasNext()){
					Entry<Integer, List<TestCase>> entry = it.next();
					System.out.print(entry.getKey() + ": ");
					for(TestCase tc: entry.getValue()){
						System.out.print(mTS.getValueTestCase(tc) + ", ");
					}
					System.out.println();
				}
				
				mapTS.setMapFault(map);
				System.out.println("============================");
				System.out.println(mapTS.toStringMapFaultTCs());
				System.out.println("+++++++++++++++++++++++++");
				mapTS.toStringMapTCFaults();
			}catch (LTSBTException e){
				e.printStackTrace();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}

		}
		
	}

}
